WE begin by importing the data and set our working directory and doing a bit of data cleaning

library(tidyverse)
Registered S3 methods overwritten by 'dbplyr':
  method         from
  print.tbl_lazy     
  print.tbl_sql      
-- Attaching packages ------------------------------------------- tidyverse 1.3.1 --
v ggplot2 3.3.5     v purrr   0.3.4
v tibble  3.1.3     v dplyr   1.0.7
v tidyr   1.1.3     v stringr 1.4.0
v readr   2.0.1     v forcats 0.5.1
-- Conflicts ---------------------------------------------- tidyverse_conflicts() --
x dplyr::filter() masks stats::filter()
x dplyr::lag()    masks stats::lag()
#if(!requireNamespace("devtools")) install.packages("devtools")
#devtools::install_github("dkahle/ggmap")

setwd("C:\\Users\\lydia\\OneDrive\\ASS2CETM47AGIAN")
train_raw <- read_csv("FinalMove.csv")
New names:
* `` -> ...1
One or more parsing issues, see `problems()` for detailsRows: 1000 Columns: 14
-- Column specification ------------------------------------------------------------
Delimiter: ","
chr (7): address, propertyType, detailUrl, details, displayPrice, dateSold, tenure
dbl (5): ...1, bedrooms, images.count, location.lat, location.lng
lgl (2): hasFloorPlan, newBuild

i Use `spec()` to retrieve the full column specification for this data.
i Specify the column types or set `show_col_types = FALSE` to quiet this message.

lets select only the columns we need for our analysis

train_raw <- train_raw[c('address','propertyType','bedrooms','hasFloorPlan','location.lat','location.lng','details','displayPrice','dateSold','tenure','newBuild')]

lets see what we have

head(train_raw)

DATA CLEANING

we use regular expressioins to remove weird characters from our detail columns

train_raw$displayPrice <- gsub('£','',train_raw$displayPrice)
train_raw$displayPrice <- gsub(',','',train_raw$displayPrice)
train_raw$details <- gsub('<ul class="keyfeatures">', '', train_raw$details)
train_raw$details <- gsub('<li>', '', train_raw$details)
train_raw$details <- gsub('</li>', ' ', train_raw$details)
train_raw$details <- gsub('</ul>', ' ', train_raw$details)

we make change some columns to numeric, and factors others

train_raw$displayPrice <- as.numeric(train_raw$displayPrice)

train_raw$tenure <- as.factor(train_raw$tenure)
train_raw$address <- as.factor(train_raw$address)
train_raw$dateSold <- gsub(' ', '', train_raw$dateSold)
train_raw$dateSold <- as.Date(train_raw$dateSold,"%d%b%Y")

we format the dates to a usable format

train_raw$dateSold <- as.Date(train_raw$dateSold,"%d%b%Y" )

lets correct a mistake i made during scrapping

library(naniar)

train_raw <- replace_with_na_all(train_raw,
                       condition = ~.x %in% c("Na"))
train_raw <- train_raw %>% drop_na() 

Lets create bins of our prices to visualise the data better

df1 <- data.frame(train_raw$displayPrice,bin=cut(train_raw$displayPrice,c(50000,
                                                      100000,
                                                      150000,
                                                      250000,
                                                      400000,
                                                      700000,
                                                      1200000,
                                                      2000000
                                                      ),include.lowest=TRUE))

train_raw$bin <- df1$bin

EXPLORATORY ANALYSIS

so far our house prices seem to be spread well. with most of the houses being in the 50 to 100 pound range.

100 to 150 are also simliar to the lower group wecan see that the closer you get to southshields and the beach the higher the prices

the 150 to 400 hundred thousand ones denoted in green and turquoise seem to be consentrated in barnes area and toward roker

barns area etc.. while the expensive 700 thousand plus seem to be mostly in south shields

library(ggmap)

price_plot <- qmplot(location.lng, location.lat, data = train_raw, maptype = "toner-lite", color = bin)
Using zoom = 13...
price_plot

we can see the prices are definitley impacted by location, but also the type of house. whether terraced or not.

qmplot(location.lng, location.lat, data = train_raw, color = bin) + 
  facet_wrap(~ propertyType)
Using zoom = 13...

most houses seem to be old builds. and doesnt affect the price


  qmplot(location.lng, location.lat, data = train_raw, color = bin) + 
  facet_wrap(~ newBuild)
Using zoom = 13...

Lets use our house descriptions to see if there is any correlation to price

set.seed(345)
library(tidytext)
library(wordcloud)
library(RColorBrewer)

sunderland_tidy <- train_raw %>%

   unnest_tokens(word, details) %>% #tokenize words
   anti_join(get_stopwords()) #remove stop words

words <- sunderland_tidy  %>%
   count(word, sort = TRUE)


#create word cloud
set.seed(1234) 
 wordcloud(words = words$word, freq = words$n, min.n = 1,           
          max.words=200, random.order=FALSE, rot.per=0.35,            
          colors=brewer.pal(8, "Dark2"))

Now lets find out the top words used per price bin

top_words <-
  sunderland_tidy %>%
  count(word, sort = TRUE) %>%
  filter(!word %in% as.character(1:5)) %>% #remove characters 1-5,they are already in the text
  slice_max(n, n = 100) %>% # select top 100
  pull(word)

#now lets count those words changing per bin 
word_freqs <-
  sunderland_tidy %>%
  count(word, displayPrice) %>%  # how mny times is each word used per price range
  complete(word, displayPrice, fill = list(n = 0)) %>% # if those words are missing just put zero
  group_by(displayPrice) %>%
  mutate(                      
    price_total = sum(n),#how many words are used total for each price range in a proportion
    proportion = n / price_total
  ) %>%
  ungroup() %>%
  filter(word %in% top_words)
word_freqs

lets train some linear models to find the words that are increasing with price and those that are decreasing with price

word_mods <-
  word_freqs %>%
  nest(data = c(displayPrice, n, price_total, proportion)) %>%
  mutate(
    model = map(data, ~ glm(cbind(n, price_total) ~ displayPrice, ., family = "binomial")),
    #we want to know if the number of success depend on price range
    #we create multiple models over the data that we nested above
    model = map(model, tidy)#create dataframes out of the models instead of the models themselves
  ) %>%
  unnest(model) %>%
  filter(term == "displayPrice") %>%
  mutate(p.value = p.adjust(p.value)) %>% #adjust the p values since we trained a lot of models
  arrange(-estimate)# - sign is order of prices this is descending order
#estimate is the effect the words have on the slope(expensive or cheap)
word_mods

We can Create a visualization for this

library(ggrepel)

word_mods %>%
   ggplot(aes(estimate, p.value)) +
   geom_vline(xintercept = 0, lty = 2, alpha = 0.7, color = "gray50") +
   geom_point(color ="brown", alpha = 0.8, size = 1.5) +
   scale_y_log10() +
   geom_text_repel(aes(label = word)) +
   labs(title = "WORD VALUE")

NA
NA

lets create different dataframes for the cheap and expensive words

higher_words <-
  word_mods %>%
  filter(p.value < 0.05) %>%
  slice_max(estimate, n = 12) %>%
  pull(word)

lower_words <-
  word_mods %>%
  filter(p.value < 0.05) %>%
  slice_max(-estimate, n = 12) %>%
  pull(word)

We can observe how proportion of words change per price range we have poportion of words on the Y axes and price range on the X axes

word_freqs %>%
  filter(word %in% lower_words) %>%
  ggplot(aes(displayPrice, proportion, color = word)) +
  geom_line(size = 0.5, alpha = 0.7, show.legend = FALSE) +
  facet_wrap(vars(word), scales = "free_y") +
  scale_x_continuous(labels = scales::dollar) +
  scale_y_continuous(labels = scales::percent, limits = c(0, NA)) +
  labs(x = NULL, y = "proportion of total words used for homes at that price") +
  theme_light(base_family = "IBMPlexSans")

before i model i want to see the distribution of my data to see what model i should use

library(skimr)

Attaching package: 㤼㸱skimr㤼㸲

The following object is masked from 㤼㸱package:naniar㤼㸲:

    n_complete
skim(train_raw)
-- Data Summary ------------------------
                           Values   
Name                       train_raw
Number of rows             648      
Number of columns          12       
_______________________             
Column type frequency:              
  character                2        
  Date                     1        
  factor                   3        
  logical                  2        
  numeric                  4        
________________________            
Group variables            None     

-- Variable type: character -------------------------------------------------------------------------
# A tibble: 2 x 8
  skim_variable n_missing complete_rate   min   max empty n_unique whitespace
* <chr>             <int>         <dbl> <int> <int> <int>    <int>      <int>
1 propertyType          0             1     4    13     0        4          0
2 details               0             1    19   611     0      603          0

-- Variable type: Date ------------------------------------------------------------------------------
# A tibble: 1 x 7
  skim_variable n_missing complete_rate min        max        median     n_unique
* <chr>             <int>         <dbl> <date>     <date>     <date>        <int>
1 dateSold              0             1 2020-11-11 2021-07-16 2021-02-11      122

-- Variable type: factor ----------------------------------------------------------------------------
# A tibble: 3 x 6
  skim_variable n_missing complete_rate ordered n_unique top_counts                           
* <chr>             <int>         <dbl> <lgl>      <int> <chr>                                
1 address               0             1 FALSE        609 1, : 2, 10,: 2, 11,: 2, 134: 2       
2 tenure                0             1 FALSE          2 Fre: 528, Lea: 120                   
3 bin                   0             1 FALSE          6 (1.: 184, [5e: 180, (1e: 156, (2.: 92

-- Variable type: logical ---------------------------------------------------------------------------
# A tibble: 2 x 5
  skim_variable n_missing complete_rate  mean count             
* <chr>             <int>         <dbl> <dbl> <chr>             
1 hasFloorPlan          0             1 0.525 TRU: 340, FAL: 308
2 newBuild              0             1 0     FAL: 648          

-- Variable type: numeric ---------------------------------------------------------------------------
# A tibble: 4 x 11
  skim_variable n_missing complete_rate      mean          sd       p0      p25       p50       p75
* <chr>             <int>         <dbl>     <dbl>       <dbl>    <dbl>    <dbl>     <dbl>     <dbl>
1 bedrooms              0             1      2.94      0.888      0        2         3         3   
2 location.lat          0             1     54.9       0.0268    54.9     54.9      54.9      54.9 
3 location.lng          0             1     -1.40      0.0269    -1.46    -1.42     -1.40     -1.38
4 displayPrice          0             1 178743.   113378.     50000    97000    147250    225000   
       p100 hist 
*     <dbl> <chr>
1      7    ▁▃▇▁▁
2     55.0  ▅▇▆▇▃
3     -1.36 ▂▅▆▆▇
4 793777    ▇▂▁▁▁
hist(train_raw$displayPrice, breaks=12, col="red",
     main="House prices distribution",)

MODELLING

we create our splits and cross validation folds

library(tidymodels)

set.seed(123)
sunderland_split <- train_raw %>%
  mutate(details = str_to_lower(details)) %>%
  initial_split(strata = bin) #stratafied sampling

sunderland_train <- training(sunderland_split)
sunderland_test <- testing(sunderland_split)
sunderland_metrics <- metric_set(rmse,mae)


set.seed(234)
sunderland_folds <- vfold_cv(sunderland_train, v = 20, strata = bin)# 5 to reduce tuning time

higher_pat <- glue::glue_collapse(higher_words, sep = "|") # use the collape to make them usable with regex() 
lower_pat <- glue::glue_collapse(lower_words, sep = "|")
sunderland_folds
#  20-fold cross-validation using stratification 

For feature engineering, let’s use basically everything in the dataset (aside from city, which was not a very useful variable) and create dummy or indicator variables using step_regex(). The idea here is that we will detect whether these words associated with low/high price are there and create a yes/no variable indicating their presence or absence.

library(themis)

sunderland_rec <-
  recipe(displayPrice ~ ., data = sunderland_train) %>% 
  
  step_upsample(bin) %>%
  
  update_role(bin, new_role = "Range") %>% 
  update_role(address, new_role = "ID") %>% 
  # keep but dont us bin as predictor or outcome
  

  step_regex(details, pattern = higher_pat, result = "high_price_words") %>% 
  #we use this to make new variables out of our words
  step_regex(details, pattern = lower_pat, result = "low_price_words") %>%

  step_rm(details) %>%
  
  step_date(dateSold, features = c("month")) %>%
  #see if month has effects
  step_naomit(displayPrice) %>%
  update_role(dateSold, new_role = "dayofsale")%>%
  
  step_dummy(all_nominal_predictors()) %>%
  # this  codes the categorical with multiple factors into multiple variables
  
  step_nzv(all_predictors()) # removes highly spared and unbalanced data 


sunderland_rec %>% prep()%>%
  juice()

lets create our model specifications and our workflow fmjfnb

xgb_spec <-
  boost_tree(
    trees = 1000,
    tree_depth = tune(),
    min_n = tune(),
    mtry = tune(),
    sample_size = tune(),
    learn_rate = tune()
  ) %>%
  set_engine("xgboost") %>%
  set_mode("regression")

xgb_word_wf <- workflow(sunderland_rec, xgb_spec)

lets Create a Grid that we will use to tune our work flow and models

set.seed(123)
xgb_grid <-
  grid_max_entropy(
    tree_depth(c(5L, 10L)),
    min_n(c(10L, 40L)),
    mtry(c(5L, 10L)),
    sample_prop(c(0.5, 1.0)),
    learn_rate(c(-2, -1)),
    size = 20
  )
#this grid specifies how many parameters we want to try, 
#max entropy simply means try as much efficiently as possible without covering the whole grid
#bellow are all those parameters its tyying

Now lets train and tune our models and work flows tune grid tunes quickly by throwing away tuning folds that are unlikely to perform well based on their similarity to other tuning instances that didn’t go well

library(finetune)
doParallel::registerDoParallel()

set.seed(234)
xgb_word_rs <-
  tune_grid(
    object = xgb_word_wf,
    resamples = sunderland_folds,
    grid = xgb_grid,
    metrics = sunderland_metrics,
    control = tune::control_grid(save_pred = TRUE)
  )

xgb_word_rs
# Tuning results
# 20-fold cross-validation using stratification 

EVALUATION

lets see which model performed best

show_best(xgb_word_rs)
No value of `metric` was given; metric 'rmse' will be used.

lets do final fit on best model

set.seed(123)
xgb_last <-
  xgb_word_wf %>%
  finalize_workflow(select_best(xgb_word_rs,"rmse","rsq")) %>%
  last_fit(sunderland_split)
xgb_last
# Resampling results
# Manual resampling 
meta <- collect_predictions(xgb_last)
meta
mets <- collect_metrics(xgb_last)
mets

lets find metrics

library(caret)
mse1 = mean((meta$displayPrice - meta$.pred)^2)
mae1 = caret::MAE(meta$displayPrice, meta$.pred)
rmse1 = caret::RMSE(meta$displayPrice, meta$.pred)
mse1
[1] 2380815089
mae1
[1] 34699.65
rmse1
[1] 48793.6
maxim <- max(meta$displayPrice)
minim <- min(meta$displayPrice)

norm_rmse <- rmse1/(maxim - minim)
norm_rmse
[1] 0.077703
x = 1:length(meta$displayPrice)
plot(x, meta$displayPrice, col = "red", type = "l")
lines(x, meta$.pred, col = "blue", type = "l")
legend(x = 1, y = 38,  legend = c("original test_y", "predicted test_y"), 
       col = c("red", "blue"), box.lty = 1, cex = 0.8, lty = c(1, 1))


ggplot(meta,                                     # Draw plot using ggplot2 package
       aes(x = .pred,
           y = displayPrice,fill= displayPrice)) +
  geom_point(alpha=0.5, size=2, shape=21) +
  scale_fill_viridis_c() +
  geom_smooth(method = "lm", alpha = .15, aes(fill = displayPrice)) 

NA
NA
NA

we can plot attribute importance

library(vip)

Attaching package: 㤼㸱vip㤼㸲

The following object is masked from 㤼㸱package:utils㤼㸲:

    vi
 extract_workflow(xgb_last) %>%
  extract_fit_parsnip() %>%
  vip(geom = "point", num_features = 20, 
      mapping = aes_string(color = "Importance", size = 1)) +
      scale_fill_viridis_c() +
      scale_y_log10() +
      ggtitle("IMPORTANCE")

-The high price words are not really being used but interestingly the lower price words are being used.

-maybe its because lower price words use more description in their listings as we saw in the volacno plot

-we can see that the model did better for echeaper houses than cheaper ones. might be because they ar more in number and are clamped together more than expensive ones.

-in the end the word descriptions did not matter as much as the ones in data frame.

-location latitude was most important. this is consistent with the map because the further east you are the closer you are to the water.

                                 Discussion

from the regression line, we can see that the expensive houses are too outlying if we had removed them, we could have gotten higher rmse. the plot comfimrs it. 77% rsq means there are more attribute we could be missing.

LS0tDQp0aXRsZTogIlN1bmRlcmxhbmQgUHJpY2UgcHJlZGljdGlvbiINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCg0KDQoNCg0KV0UgYmVnaW4gYnkgaW1wb3J0aW5nIHRoZSBkYXRhIGFuZCBzZXQgb3VyIHdvcmtpbmcgZGlyZWN0b3J5IGFuZCBkb2luZyBhIGJpdCBvZiBkYXRhDQpjbGVhbmluZw0KDQpgYGB7cn0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KDQojaWYoIXJlcXVpcmVOYW1lc3BhY2UoImRldnRvb2xzIikpIGluc3RhbGwucGFja2FnZXMoImRldnRvb2xzIikNCiNkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoImRrYWhsZS9nZ21hcCIpDQoNCnNldHdkKCJDOlxcVXNlcnNcXGx5ZGlhXFxPbmVEcml2ZVxcQVNTMkNFVE00N0FHSUFOIikNCnRyYWluX3JhdyA8LSByZWFkX2NzdigiRmluYWxNb3ZlLmNzdiIpDQoNCmBgYA0KDQpsZXRzIHNlbGVjdCBvbmx5IHRoZSBjb2x1bW5zIHdlIG5lZWQgZm9yIG91ciBhbmFseXNpcw0KYGBge3J9DQp0cmFpbl9yYXcgPC0gdHJhaW5fcmF3W2MoJ2FkZHJlc3MnLCdwcm9wZXJ0eVR5cGUnLCdiZWRyb29tcycsJ2hhc0Zsb29yUGxhbicsJ2xvY2F0aW9uLmxhdCcsJ2xvY2F0aW9uLmxuZycsJ2RldGFpbHMnLCdkaXNwbGF5UHJpY2UnLCdkYXRlU29sZCcsJ3RlbnVyZScsJ25ld0J1aWxkJyldDQpgYGANCg0KbGV0cyBzZWUgd2hhdCB3ZSBoYXZlDQpgYGB7cn0NCmhlYWQodHJhaW5fcmF3KQ0KYGBgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgREFUQSBDTEVBTklORw0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCg0Kd2UgdXNlIHJlZ3VsYXIgZXhwcmVzc2lvaW5zIHRvIHJlbW92ZSB3ZWlyZCBjaGFyYWN0ZXJzIGZyb20gb3VyIGRldGFpbCBjb2x1bW5zDQpgYGB7cn0NCnRyYWluX3JhdyRkaXNwbGF5UHJpY2UgPC0gZ3N1YignwqMnLCcnLHRyYWluX3JhdyRkaXNwbGF5UHJpY2UpDQp0cmFpbl9yYXckZGlzcGxheVByaWNlIDwtIGdzdWIoJywnLCcnLHRyYWluX3JhdyRkaXNwbGF5UHJpY2UpDQp0cmFpbl9yYXckZGV0YWlscyA8LSBnc3ViKCc8dWwgY2xhc3M9ImtleWZlYXR1cmVzIj4nLCAnJywgdHJhaW5fcmF3JGRldGFpbHMpDQp0cmFpbl9yYXckZGV0YWlscyA8LSBnc3ViKCc8bGk+JywgJycsIHRyYWluX3JhdyRkZXRhaWxzKQ0KdHJhaW5fcmF3JGRldGFpbHMgPC0gZ3N1YignPC9saT4nLCAnICcsIHRyYWluX3JhdyRkZXRhaWxzKQ0KdHJhaW5fcmF3JGRldGFpbHMgPC0gZ3N1YignPC91bD4nLCAnICcsIHRyYWluX3JhdyRkZXRhaWxzKQ0KYGBgDQoNCndlIG1ha2UgY2hhbmdlIHNvbWUgY29sdW1ucyB0byBudW1lcmljLCBhbmQgZmFjdG9ycyBvdGhlcnMNCmBgYHtyfQ0KdHJhaW5fcmF3JGRpc3BsYXlQcmljZSA8LSBhcy5udW1lcmljKHRyYWluX3JhdyRkaXNwbGF5UHJpY2UpDQoNCnRyYWluX3JhdyR0ZW51cmUgPC0gYXMuZmFjdG9yKHRyYWluX3JhdyR0ZW51cmUpDQp0cmFpbl9yYXckYWRkcmVzcyA8LSBhcy5mYWN0b3IodHJhaW5fcmF3JGFkZHJlc3MpDQp0cmFpbl9yYXckZGF0ZVNvbGQgPC0gZ3N1YignICcsICcnLCB0cmFpbl9yYXckZGF0ZVNvbGQpDQp0cmFpbl9yYXckZGF0ZVNvbGQgPC0gYXMuRGF0ZSh0cmFpbl9yYXckZGF0ZVNvbGQsIiVkJWIlWSIpDQpgYGANCg0KDQp3ZSBmb3JtYXQgdGhlIGRhdGVzIHRvIGEgdXNhYmxlIGZvcm1hdA0KYGBge3J9DQp0cmFpbl9yYXckZGF0ZVNvbGQgPC0gYXMuRGF0ZSh0cmFpbl9yYXckZGF0ZVNvbGQsIiVkJWIlWSIgKQ0KYGBgDQoNCmxldHMgY29ycmVjdCBhIG1pc3Rha2UgaSBtYWRlIGR1cmluZyBzY3JhcHBpbmcNCmBgYHtyfQ0KbGlicmFyeShuYW5pYXIpDQoNCnRyYWluX3JhdyA8LSByZXBsYWNlX3dpdGhfbmFfYWxsKHRyYWluX3JhdywNCiAgICAgICAgICAgICAgICAgICAgICAgY29uZGl0aW9uID0gfi54ICVpbiUgYygiTmEiKSkNCnRyYWluX3JhdyA8LSB0cmFpbl9yYXcgJT4lIGRyb3BfbmEoKSANCmBgYA0KDQoNCg0KTGV0cyBjcmVhdGUgYmlucyBvZiBvdXIgcHJpY2VzIHRvIHZpc3VhbGlzZSB0aGUgZGF0YSBiZXR0ZXINCmBgYHtyfQ0KZGYxIDwtIGRhdGEuZnJhbWUodHJhaW5fcmF3JGRpc3BsYXlQcmljZSxiaW49Y3V0KHRyYWluX3JhdyRkaXNwbGF5UHJpY2UsYyg1MDAwMCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDEwMDAwMCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDE1MDAwMCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDI1MDAwMCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDQwMDAwMCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDcwMDAwMCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDEyMDAwMDAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAyMDAwMDAwDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApLGluY2x1ZGUubG93ZXN0PVRSVUUpKQ0KDQp0cmFpbl9yYXckYmluIDwtIGRmMSRiaW4NCmBgYA0KDQoNCg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEVYUExPUkFUT1JZIEFOQUxZU0lTDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KDQpzbyBmYXIgb3VyIGhvdXNlIHByaWNlcyBzZWVtIHRvIGJlIHNwcmVhZCB3ZWxsLiB3aXRoIG1vc3Qgb2YgdGhlIGhvdXNlcyBiZWluZw0KaW4gdGhlIDUwIHRvIDEwMCBwb3VuZCByYW5nZS4gDQoNCjEwMCB0byAxNTAgYXJlIGFsc28gc2ltbGlhciB0byB0aGUgbG93ZXIgZ3JvdXANCndlY2FuIHNlZSB0aGF0IHRoZSBjbG9zZXIgeW91IGdldCB0byBzb3V0aHNoaWVsZHMgYW5kIHRoZSBiZWFjaCB0aGUgaGlnaGVyIHRoZSBwcmljZXMNCg0KdGhlIDE1MCB0byA0MDAgaHVuZHJlZCB0aG91c2FuZCBvbmVzIGRlbm90ZWQgaW4gZ3JlZW4gYW5kIHR1cnF1b2lzZQ0Kc2VlbSB0byBiZSBjb25zZW50cmF0ZWQgaW4gYmFybmVzIGFyZWEgYW5kIHRvd2FyZCByb2tlcg0KDQpiYXJucyBhcmVhIGV0Yy4uIHdoaWxlIHRoZQ0KZXhwZW5zaXZlIDcwMCB0aG91c2FuZCBwbHVzIHNlZW0gdG8gYmUgbW9zdGx5IGluIHNvdXRoIHNoaWVsZHMNCmBgYHtyfQ0KbGlicmFyeShnZ21hcCkNCg0KcHJpY2VfcGxvdCA8LSBxbXBsb3QobG9jYXRpb24ubG5nLCBsb2NhdGlvbi5sYXQsIGRhdGEgPSB0cmFpbl9yYXcsIG1hcHR5cGUgPSAidG9uZXItbGl0ZSIsIGNvbG9yID0gYmluKQ0KDQpwcmljZV9wbG90DQpgYGANCg0KDQoNCndlIGNhbiBzZWUgdGhlIHByaWNlcyBhcmUgZGVmaW5pdGxleSBpbXBhY3RlZCBieSBsb2NhdGlvbiwgYnV0IGFsc28gdGhlIHR5cGUNCm9mIGhvdXNlLiB3aGV0aGVyIHRlcnJhY2VkIG9yIG5vdC4NCmBgYHtyfQ0KcW1wbG90KGxvY2F0aW9uLmxuZywgbG9jYXRpb24ubGF0LCBkYXRhID0gdHJhaW5fcmF3LCBjb2xvciA9IGJpbikgKyANCiAgZmFjZXRfd3JhcCh+IHByb3BlcnR5VHlwZSkNCmBgYA0KbW9zdCBob3VzZXMgc2VlbSB0byBiZSBvbGQgYnVpbGRzLiBhbmQgZG9lc250IGFmZmVjdCB0aGUgcHJpY2UNCmBgYHtyIG1lc3NhZ2U9VFJVRSwgd2FybmluZz1UUlVFfQ0KDQogIHFtcGxvdChsb2NhdGlvbi5sbmcsIGxvY2F0aW9uLmxhdCwgZGF0YSA9IHRyYWluX3JhdywgY29sb3IgPSBiaW4pICsgDQogIGZhY2V0X3dyYXAofiBuZXdCdWlsZCkNCg0KYGBgDQoNCg0KTGV0cyB1c2Ugb3VyIGhvdXNlIGRlc2NyaXB0aW9ucyB0byBzZWUgaWYgdGhlcmUgaXMgYW55IGNvcnJlbGF0aW9uIHRvIHByaWNlDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0Kc2V0LnNlZWQoMzQ1KQ0KbGlicmFyeSh0aWR5dGV4dCkNCmxpYnJhcnkod29yZGNsb3VkKQ0KbGlicmFyeShSQ29sb3JCcmV3ZXIpDQoNCnN1bmRlcmxhbmRfdGlkeSA8LSB0cmFpbl9yYXcgJT4lDQoNCiAgIHVubmVzdF90b2tlbnMod29yZCwgZGV0YWlscykgJT4lICN0b2tlbml6ZSB3b3Jkcw0KICAgYW50aV9qb2luKGdldF9zdG9wd29yZHMoKSkgI3JlbW92ZSBzdG9wIHdvcmRzDQoNCndvcmRzIDwtIHN1bmRlcmxhbmRfdGlkeSAgJT4lDQogICBjb3VudCh3b3JkLCBzb3J0ID0gVFJVRSkNCg0KDQojY3JlYXRlIHdvcmQgY2xvdWQNCnNldC5zZWVkKDEyMzQpIA0KIHdvcmRjbG91ZCh3b3JkcyA9IHdvcmRzJHdvcmQsIGZyZXEgPSB3b3JkcyRuLCBtaW4ubiA9IDEsICAgICAgICAgICANCiAgICAgICAgICBtYXgud29yZHM9MjAwLCByYW5kb20ub3JkZXI9RkFMU0UsIHJvdC5wZXI9MC4zNSwgICAgICAgICAgICANCiAgICAgICAgICBjb2xvcnM9YnJld2VyLnBhbCg4LCAiRGFyazIiKSkNCmBgYA0KDQoNCk5vdyBsZXRzIGZpbmQgb3V0IHRoZSB0b3Agd29yZHMgdXNlZCBwZXIgcHJpY2UgYmluDQpgYGB7cn0NCnRvcF93b3JkcyA8LQ0KICBzdW5kZXJsYW5kX3RpZHkgJT4lDQogIGNvdW50KHdvcmQsIHNvcnQgPSBUUlVFKSAlPiUNCiAgZmlsdGVyKCF3b3JkICVpbiUgYXMuY2hhcmFjdGVyKDE6NSkpICU+JSAjcmVtb3ZlIGNoYXJhY3RlcnMgMS01LHRoZXkgYXJlIGFscmVhZHkgaW4gdGhlIHRleHQNCiAgc2xpY2VfbWF4KG4sIG4gPSAxMDApICU+JSAjIHNlbGVjdCB0b3AgMTAwDQogIHB1bGwod29yZCkNCg0KI25vdyBsZXRzIGNvdW50IHRob3NlIHdvcmRzIGNoYW5naW5nIHBlciBiaW4gDQp3b3JkX2ZyZXFzIDwtDQogIHN1bmRlcmxhbmRfdGlkeSAlPiUNCiAgY291bnQod29yZCwgZGlzcGxheVByaWNlKSAlPiUgICMgaG93IG1ueSB0aW1lcyBpcyBlYWNoIHdvcmQgdXNlZCBwZXIgcHJpY2UgcmFuZ2UNCiAgY29tcGxldGUod29yZCwgZGlzcGxheVByaWNlLCBmaWxsID0gbGlzdChuID0gMCkpICU+JSAjIGlmIHRob3NlIHdvcmRzIGFyZSBtaXNzaW5nIGp1c3QgcHV0IHplcm8NCiAgZ3JvdXBfYnkoZGlzcGxheVByaWNlKSAlPiUNCiAgbXV0YXRlKCAgICAgICAgICAgICAgICAgICAgICANCiAgICBwcmljZV90b3RhbCA9IHN1bShuKSwjaG93IG1hbnkgd29yZHMgYXJlIHVzZWQgdG90YWwgZm9yIGVhY2ggcHJpY2UgcmFuZ2UgaW4gYSBwcm9wb3J0aW9uDQogICAgcHJvcG9ydGlvbiA9IG4gLyBwcmljZV90b3RhbA0KICApICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIGZpbHRlcih3b3JkICVpbiUgdG9wX3dvcmRzKQ0Kd29yZF9mcmVxcw0KYGBgDQoNCg0KbGV0cyB0cmFpbiBzb21lIGxpbmVhciBtb2RlbHMgdG8gZmluZCB0aGUgd29yZHMgdGhhdCBhcmUgaW5jcmVhc2luZyB3aXRoIHByaWNlIGFuZCB0aG9zZSB0aGF0IGFyZSBkZWNyZWFzaW5nIHdpdGggcHJpY2UNCg0KYGBge3J9DQp3b3JkX21vZHMgPC0NCiAgd29yZF9mcmVxcyAlPiUNCiAgbmVzdChkYXRhID0gYyhkaXNwbGF5UHJpY2UsIG4sIHByaWNlX3RvdGFsLCBwcm9wb3J0aW9uKSkgJT4lDQogIG11dGF0ZSgNCiAgICBtb2RlbCA9IG1hcChkYXRhLCB+IGdsbShjYmluZChuLCBwcmljZV90b3RhbCkgfiBkaXNwbGF5UHJpY2UsIC4sIGZhbWlseSA9ICJiaW5vbWlhbCIpKSwNCiAgICAjd2Ugd2FudCB0byBrbm93IGlmIHRoZSBudW1iZXIgb2Ygc3VjY2VzcyBkZXBlbmQgb24gcHJpY2UgcmFuZ2UNCiAgICAjd2UgY3JlYXRlIG11bHRpcGxlIG1vZGVscyBvdmVyIHRoZSBkYXRhIHRoYXQgd2UgbmVzdGVkIGFib3ZlDQogICAgbW9kZWwgPSBtYXAobW9kZWwsIHRpZHkpI2NyZWF0ZSBkYXRhZnJhbWVzIG91dCBvZiB0aGUgbW9kZWxzIGluc3RlYWQgb2YgdGhlIG1vZGVscyB0aGVtc2VsdmVzDQogICkgJT4lDQogIHVubmVzdChtb2RlbCkgJT4lDQogIGZpbHRlcih0ZXJtID09ICJkaXNwbGF5UHJpY2UiKSAlPiUNCiAgbXV0YXRlKHAudmFsdWUgPSBwLmFkanVzdChwLnZhbHVlKSkgJT4lICNhZGp1c3QgdGhlIHAgdmFsdWVzIHNpbmNlIHdlIHRyYWluZWQgYSBsb3Qgb2YgbW9kZWxzDQogIGFycmFuZ2UoLWVzdGltYXRlKSMgLSBzaWduIGlzIG9yZGVyIG9mIHByaWNlcyB0aGlzIGlzIGRlc2NlbmRpbmcgb3JkZXINCiNlc3RpbWF0ZSBpcyB0aGUgZWZmZWN0IHRoZSB3b3JkcyBoYXZlIG9uIHRoZSBzbG9wZShleHBlbnNpdmUgb3IgY2hlYXApDQp3b3JkX21vZHMNCmBgYA0KDQoNCldlIGNhbiBDcmVhdGUgYSB2aXN1YWxpemF0aW9uIGZvciB0aGlzDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeShnZ3JlcGVsKQ0KDQp3b3JkX21vZHMgJT4lDQogICBnZ3Bsb3QoYWVzKGVzdGltYXRlLCBwLnZhbHVlKSkgKw0KICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMCwgbHR5ID0gMiwgYWxwaGEgPSAwLjcsIGNvbG9yID0gImdyYXk1MCIpICsNCiAgIGdlb21fcG9pbnQoY29sb3IgPSJicm93biIsIGFscGhhID0gMC44LCBzaXplID0gMS41KSArDQogICBzY2FsZV95X2xvZzEwKCkgKw0KICAgZ2VvbV90ZXh0X3JlcGVsKGFlcyhsYWJlbCA9IHdvcmQpKSArDQogICBsYWJzKHRpdGxlID0gIldPUkQgVkFMVUUiKQ0KDQoNCmBgYA0KDQoNCmxldHMgY3JlYXRlIGRpZmZlcmVudCBkYXRhZnJhbWVzIGZvciB0aGUgY2hlYXAgYW5kIGV4cGVuc2l2ZSB3b3Jkcw0KYGBge3J9DQpoaWdoZXJfd29yZHMgPC0NCiAgd29yZF9tb2RzICU+JQ0KICBmaWx0ZXIocC52YWx1ZSA8IDAuMDUpICU+JQ0KICBzbGljZV9tYXgoZXN0aW1hdGUsIG4gPSAxMikgJT4lDQogIHB1bGwod29yZCkNCg0KbG93ZXJfd29yZHMgPC0NCiAgd29yZF9tb2RzICU+JQ0KICBmaWx0ZXIocC52YWx1ZSA8IDAuMDUpICU+JQ0KICBzbGljZV9tYXgoLWVzdGltYXRlLCBuID0gMTIpICU+JQ0KICBwdWxsKHdvcmQpDQpgYGANCg0KDQpXZSBjYW4gb2JzZXJ2ZSBob3cgcHJvcG9ydGlvbiBvZiB3b3JkcyBjaGFuZ2UgcGVyIHByaWNlIHJhbmdlDQp3ZSBoYXZlIHBvcG9ydGlvbiBvZiB3b3JkcyBvbiB0aGUgWSBheGVzIGFuZCBwcmljZSByYW5nZSBvbiB0aGUgWCBheGVzDQoNCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCndvcmRfZnJlcXMgJT4lDQogIGZpbHRlcih3b3JkICVpbiUgbG93ZXJfd29yZHMpICU+JQ0KICBnZ3Bsb3QoYWVzKGRpc3BsYXlQcmljZSwgcHJvcG9ydGlvbiwgY29sb3IgPSB3b3JkKSkgKw0KICBnZW9tX2xpbmUoc2l6ZSA9IDAuNSwgYWxwaGEgPSAwLjcsIHNob3cubGVnZW5kID0gRkFMU0UpICsNCiAgZmFjZXRfd3JhcCh2YXJzKHdvcmQpLCBzY2FsZXMgPSAiZnJlZV95IikgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpkb2xsYXIpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6cGVyY2VudCwgbGltaXRzID0gYygwLCBOQSkpICsNCiAgbGFicyh4ID0gTlVMTCwgeSA9ICJwcm9wb3J0aW9uIG9mIHRvdGFsIHdvcmRzIHVzZWQgZm9yIGhvbWVzIGF0IHRoYXQgcHJpY2UiKSArDQogIHRoZW1lX2xpZ2h0KGJhc2VfZmFtaWx5ID0gIklCTVBsZXhTYW5zIikNCmBgYA0KDQpiZWZvcmUgaSBtb2RlbCBpIHdhbnQgdG8gc2VlIHRoZSBkaXN0cmlidXRpb24gb2YgbXkgZGF0YSB0byBzZWUgd2hhdCBtb2RlbCBpIHNob3VsZCB1c2UNCg0KYGBge3J9DQpsaWJyYXJ5KHNraW1yKQ0Kc2tpbSh0cmFpbl9yYXcpDQpgYGANCmBgYHtyfQ0KaGlzdCh0cmFpbl9yYXckZGlzcGxheVByaWNlLCBicmVha3M9MTIsIGNvbD0icmVkIiwNCiAgICAgbWFpbj0iSG91c2UgcHJpY2VzIGRpc3RyaWJ1dGlvbiIsKQ0KYGBgDQoNCg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE1PREVMTElORyANCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0Kd2UgY3JlYXRlIG91ciBzcGxpdHMgYW5kIGNyb3NzIHZhbGlkYXRpb24gZm9sZHMNCmBgYHtyfQ0KbGlicmFyeSh0aWR5bW9kZWxzKQ0KDQpzZXQuc2VlZCgxMjMpDQpzdW5kZXJsYW5kX3NwbGl0IDwtIHRyYWluX3JhdyAlPiUNCiAgbXV0YXRlKGRldGFpbHMgPSBzdHJfdG9fbG93ZXIoZGV0YWlscykpICU+JQ0KICBpbml0aWFsX3NwbGl0KHN0cmF0YSA9IGJpbikgI3N0cmF0YWZpZWQgc2FtcGxpbmcNCg0Kc3VuZGVybGFuZF90cmFpbiA8LSB0cmFpbmluZyhzdW5kZXJsYW5kX3NwbGl0KQ0Kc3VuZGVybGFuZF90ZXN0IDwtIHRlc3Rpbmcoc3VuZGVybGFuZF9zcGxpdCkNCnN1bmRlcmxhbmRfbWV0cmljcyA8LSBtZXRyaWNfc2V0KHJtc2UsbWFlKQ0KDQoNCnNldC5zZWVkKDIzNCkNCnN1bmRlcmxhbmRfZm9sZHMgPC0gdmZvbGRfY3Yoc3VuZGVybGFuZF90cmFpbiwgdiA9IDIwLCBzdHJhdGEgPSBiaW4pIyA1IHRvIHJlZHVjZSB0dW5pbmcgdGltZQ0KDQpoaWdoZXJfcGF0IDwtIGdsdWU6OmdsdWVfY29sbGFwc2UoaGlnaGVyX3dvcmRzLCBzZXAgPSAifCIpICMgdXNlIHRoZSBjb2xsYXBlIHRvIG1ha2UgdGhlbSB1c2FibGUgd2l0aCByZWdleCgpIA0KbG93ZXJfcGF0IDwtIGdsdWU6OmdsdWVfY29sbGFwc2UobG93ZXJfd29yZHMsIHNlcCA9ICJ8IikNCnN1bmRlcmxhbmRfZm9sZHMNCmBgYA0KDQpGb3IgZmVhdHVyZSBlbmdpbmVlcmluZywgbGV04oCZcyB1c2UgYmFzaWNhbGx5IGV2ZXJ5dGhpbmcgaW4gdGhlIGRhdGFzZXQgDQooYXNpZGUgZnJvbSBjaXR5LCB3aGljaCB3YXMgbm90IGEgdmVyeSB1c2VmdWwgdmFyaWFibGUpIA0KYW5kIGNyZWF0ZSBkdW1teSBvciBpbmRpY2F0b3IgdmFyaWFibGVzIHVzaW5nIHN0ZXBfcmVnZXgoKS4gDQpUaGUgaWRlYSBoZXJlIGlzIHRoYXQgd2Ugd2lsbCBkZXRlY3Qgd2hldGhlciB0aGVzZSB3b3JkcyBhc3NvY2lhdGVkIHdpdGggDQpsb3cvaGlnaCBwcmljZSBhcmUgdGhlcmUgYW5kIGNyZWF0ZSBhIHllcy9ubyB2YXJpYWJsZSBpbmRpY2F0aW5nIHRoZWlyIHByZXNlbmNlIG9yIGFic2VuY2UuDQpgYGB7cn0NCmxpYnJhcnkodGhlbWlzKQ0KDQpzdW5kZXJsYW5kX3JlYyA8LQ0KICByZWNpcGUoZGlzcGxheVByaWNlIH4gLiwgZGF0YSA9IHN1bmRlcmxhbmRfdHJhaW4pICU+JSANCiAgDQogIHN0ZXBfdXBzYW1wbGUoYmluKSAlPiUNCiAgDQogIHVwZGF0ZV9yb2xlKGJpbiwgbmV3X3JvbGUgPSAiUmFuZ2UiKSAlPiUgDQogIHVwZGF0ZV9yb2xlKGFkZHJlc3MsIG5ld19yb2xlID0gIklEIikgJT4lIA0KICAjIGtlZXAgYnV0IGRvbnQgdXMgYmluIGFzIHByZWRpY3RvciBvciBvdXRjb21lDQogIA0KDQogIHN0ZXBfcmVnZXgoZGV0YWlscywgcGF0dGVybiA9IGhpZ2hlcl9wYXQsIHJlc3VsdCA9ICJoaWdoX3ByaWNlX3dvcmRzIikgJT4lIA0KICAjd2UgdXNlIHRoaXMgdG8gbWFrZSBuZXcgdmFyaWFibGVzIG91dCBvZiBvdXIgd29yZHMNCiAgc3RlcF9yZWdleChkZXRhaWxzLCBwYXR0ZXJuID0gbG93ZXJfcGF0LCByZXN1bHQgPSAibG93X3ByaWNlX3dvcmRzIikgJT4lDQoNCiAgc3RlcF9ybShkZXRhaWxzKSAlPiUNCiAgDQogIHN0ZXBfZGF0ZShkYXRlU29sZCwgZmVhdHVyZXMgPSBjKCJtb250aCIpKSAlPiUNCiAgI3NlZSBpZiBtb250aCBoYXMgZWZmZWN0cw0KICBzdGVwX25hb21pdChkaXNwbGF5UHJpY2UpICU+JQ0KICB1cGRhdGVfcm9sZShkYXRlU29sZCwgbmV3X3JvbGUgPSAiZGF5b2ZzYWxlIiklPiUNCiAgDQogIHN0ZXBfZHVtbXkoYWxsX25vbWluYWxfcHJlZGljdG9ycygpKSAlPiUNCiAgIyB0aGlzICBjb2RlcyB0aGUgY2F0ZWdvcmljYWwgd2l0aCBtdWx0aXBsZSBmYWN0b3JzIGludG8gbXVsdGlwbGUgdmFyaWFibGVzDQogIA0KICBzdGVwX256dihhbGxfcHJlZGljdG9ycygpKSAjIHJlbW92ZXMgaGlnaGx5IHNwYXJlZCBhbmQgdW5iYWxhbmNlZCBkYXRhIA0KDQoNCnN1bmRlcmxhbmRfcmVjICU+JSBwcmVwKCklPiUNCiAganVpY2UoKQ0KYGBgDQoNCg0KbGV0cyBjcmVhdGUgb3VyIG1vZGVsIHNwZWNpZmljYXRpb25zIGFuZCBvdXIgd29ya2Zsb3cNCmZtamZuYg0KYGBge3J9DQp4Z2Jfc3BlYyA8LQ0KICBib29zdF90cmVlKA0KICAgIHRyZWVzID0gMTAwMCwNCiAgICB0cmVlX2RlcHRoID0gdHVuZSgpLA0KICAgIG1pbl9uID0gdHVuZSgpLA0KICAgIG10cnkgPSB0dW5lKCksDQogICAgc2FtcGxlX3NpemUgPSB0dW5lKCksDQogICAgbGVhcm5fcmF0ZSA9IHR1bmUoKQ0KICApICU+JQ0KICBzZXRfZW5naW5lKCJ4Z2Jvb3N0IikgJT4lDQogIHNldF9tb2RlKCJyZWdyZXNzaW9uIikNCg0KeGdiX3dvcmRfd2YgPC0gd29ya2Zsb3coc3VuZGVybGFuZF9yZWMsIHhnYl9zcGVjKQ0KYGBgDQoNCg0KDQpsZXRzIENyZWF0ZSBhIEdyaWQgdGhhdCB3ZSB3aWxsIHVzZSB0byB0dW5lIG91ciB3b3JrIGZsb3cgYW5kIG1vZGVscw0KYGBge3J9DQpzZXQuc2VlZCgxMjMpDQp4Z2JfZ3JpZCA8LQ0KICBncmlkX21heF9lbnRyb3B5KA0KICAgIHRyZWVfZGVwdGgoYyg1TCwgMTBMKSksDQogICAgbWluX24oYygxMEwsIDQwTCkpLA0KICAgIG10cnkoYyg1TCwgMTBMKSksDQogICAgc2FtcGxlX3Byb3AoYygwLjUsIDEuMCkpLA0KICAgIGxlYXJuX3JhdGUoYygtMiwgLTEpKSwNCiAgICBzaXplID0gMjANCiAgKQ0KI3RoaXMgZ3JpZCBzcGVjaWZpZXMgaG93IG1hbnkgcGFyYW1ldGVycyB3ZSB3YW50IHRvIHRyeSwgDQojbWF4IGVudHJvcHkgc2ltcGx5IG1lYW5zIHRyeSBhcyBtdWNoIGVmZmljaWVudGx5IGFzIHBvc3NpYmxlIHdpdGhvdXQgY292ZXJpbmcgdGhlIHdob2xlIGdyaWQNCiNiZWxsb3cgYXJlIGFsbCB0aG9zZSBwYXJhbWV0ZXJzIGl0cyB0cnlpbmcNCg0KDQpgYGANCg0KDQpOb3cgbGV0cyB0cmFpbiBhbmQgdHVuZSBvdXIgbW9kZWxzIGFuZCB3b3JrIGZsb3dzDQp0dW5lIGdyaWQgdHVuZXMgcXVpY2tseSBieSB0aHJvd2luZyBhd2F5IHR1bmluZyBmb2xkcyB0aGF0IGFyZSB1bmxpa2VseSB0byBwZXJmb3JtIHdlbGwgYmFzZWQNCm9uIHRoZWlyIHNpbWlsYXJpdHkgdG8gb3RoZXIgdHVuaW5nIGluc3RhbmNlcyB0aGF0IGRpZG4ndCBnbyB3ZWxsDQpgYGB7cn0NCmxpYnJhcnkoZmluZXR1bmUpDQpkb1BhcmFsbGVsOjpyZWdpc3RlckRvUGFyYWxsZWwoKQ0KDQpzZXQuc2VlZCgyMzQpDQp4Z2Jfd29yZF9ycyA8LQ0KICB0dW5lX2dyaWQoDQogICAgb2JqZWN0ID0geGdiX3dvcmRfd2YsDQogICAgcmVzYW1wbGVzID0gc3VuZGVybGFuZF9mb2xkcywNCiAgICBncmlkID0geGdiX2dyaWQsDQogICAgbWV0cmljcyA9IHN1bmRlcmxhbmRfbWV0cmljcywNCiAgICBjb250cm9sID0gdHVuZTo6Y29udHJvbF9ncmlkKHNhdmVfcHJlZCA9IFRSVUUpDQogICkNCg0KeGdiX3dvcmRfcnMNCg0KYGBgDQoNCg0KDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBFVkFMVUFUSU9ODQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQpsZXRzIHNlZSB3aGljaCBtb2RlbCBwZXJmb3JtZWQgYmVzdA0KYGBge3J9DQpzaG93X2Jlc3QoeGdiX3dvcmRfcnMpDQpgYGANCg0KDQpsZXRzIGRvIGZpbmFsIGZpdCBvbiBiZXN0IG1vZGVsDQpgYGB7cn0NCnNldC5zZWVkKDEyMykNCnhnYl9sYXN0IDwtDQogIHhnYl93b3JkX3dmICU+JQ0KICBmaW5hbGl6ZV93b3JrZmxvdyhzZWxlY3RfYmVzdCh4Z2Jfd29yZF9ycywicm1zZSIsInJzcSIpKSAlPiUNCiAgbGFzdF9maXQoc3VuZGVybGFuZF9zcGxpdCkNCmBgYA0KDQoNCmBgYHtyfQ0KeGdiX2xhc3QNCmBgYA0KDQpgYGB7cn0NCm1ldGEgPC0gY29sbGVjdF9wcmVkaWN0aW9ucyh4Z2JfbGFzdCkNCm1ldGENCmBgYA0KYGBge3J9DQptZXRzIDwtIGNvbGxlY3RfbWV0cmljcyh4Z2JfbGFzdCkNCm1ldHMNCmBgYA0KDQpsZXRzIGZpbmQgbWV0cmljcw0KYGBge3J9DQpsaWJyYXJ5KGNhcmV0KQ0KbXNlMSA9IG1lYW4oKG1ldGEkZGlzcGxheVByaWNlIC0gbWV0YSQucHJlZCleMikNCm1hZTEgPSBjYXJldDo6TUFFKG1ldGEkZGlzcGxheVByaWNlLCBtZXRhJC5wcmVkKQ0Kcm1zZTEgPSBjYXJldDo6Uk1TRShtZXRhJGRpc3BsYXlQcmljZSwgbWV0YSQucHJlZCkNCm1zZTENCm1hZTENCnJtc2UxDQpgYGANCmBgYHtyfQ0KbWF4aW0gPC0gbWF4KG1ldGEkZGlzcGxheVByaWNlKQ0KbWluaW0gPC0gbWluKG1ldGEkZGlzcGxheVByaWNlKQ0KDQpub3JtX3Jtc2UgPC0gcm1zZTEvKG1heGltIC0gbWluaW0pDQpub3JtX3Jtc2UNCmBgYA0KDQoNCmBgYHtyfQ0KeCA9IDE6bGVuZ3RoKG1ldGEkZGlzcGxheVByaWNlKQ0KcGxvdCh4LCBtZXRhJGRpc3BsYXlQcmljZSwgY29sID0gInJlZCIsIHR5cGUgPSAibCIpDQpsaW5lcyh4LCBtZXRhJC5wcmVkLCBjb2wgPSAiYmx1ZSIsIHR5cGUgPSAibCIpDQpsZWdlbmQoeCA9IDEsIHkgPSAzOCwgIGxlZ2VuZCA9IGMoIm9yaWdpbmFsIHRlc3RfeSIsICJwcmVkaWN0ZWQgdGVzdF95IiksIA0KICAgICAgIGNvbCA9IGMoInJlZCIsICJibHVlIiksIGJveC5sdHkgPSAxLCBjZXggPSAwLjgsIGx0eSA9IGMoMSwgMSkpDQpgYGANCg0KDQoNCmBgYHtyfQ0KDQpnZ3Bsb3QobWV0YSwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBEcmF3IHBsb3QgdXNpbmcgZ2dwbG90MiBwYWNrYWdlDQogICAgICAgYWVzKHggPSAucHJlZCwNCiAgICAgICAgICAgeSA9IGRpc3BsYXlQcmljZSxmaWxsPSBkaXNwbGF5UHJpY2UpKSArDQogIGdlb21fcG9pbnQoYWxwaGE9MC41LCBzaXplPTIsIHNoYXBlPTIxKSArDQogIHNjYWxlX2ZpbGxfdmlyaWRpc19jKCkgKw0KICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBhbHBoYSA9IC4xNSwgYWVzKGZpbGwgPSBkaXNwbGF5UHJpY2UpKSANCg0KDQoNCmBgYA0KDQoNCndlIGNhbiAgcGxvdCBhdHRyaWJ1dGUgaW1wb3J0YW5jZQ0KYGBge3J9DQpsaWJyYXJ5KHZpcCkNCiBleHRyYWN0X3dvcmtmbG93KHhnYl9sYXN0KSAlPiUNCiAgZXh0cmFjdF9maXRfcGFyc25pcCgpICU+JQ0KICB2aXAoZ2VvbSA9ICJwb2ludCIsIG51bV9mZWF0dXJlcyA9IDIwLCANCiAgICAgIG1hcHBpbmcgPSBhZXNfc3RyaW5nKGNvbG9yID0gIkltcG9ydGFuY2UiLCBzaXplID0gMSkpICsNCiAgICAgIHNjYWxlX2ZpbGxfdmlyaWRpc19jKCkgKw0KICAgICAgc2NhbGVfeV9sb2cxMCgpICsNCiAgICAgIGdndGl0bGUoIklNUE9SVEFOQ0UiKQ0KYGBgDQoNCi1UaGUgaGlnaCBwcmljZSB3b3JkcyBhcmUgbm90IHJlYWxseSBiZWluZyB1c2VkIGJ1dCBpbnRlcmVzdGluZ2x5IHRoZSBsb3dlciBwcmljZSB3b3JkcyANCmFyZSBiZWluZyB1c2VkLiANCg0KLW1heWJlIGl0cyBiZWNhdXNlIGxvd2VyIHByaWNlIHdvcmRzIHVzZSBtb3JlIGRlc2NyaXB0aW9uIGluIHRoZWlyIGxpc3RpbmdzIGFzIHdlIHNhdyBpbiB0aGUgdm9sYWNubyBwbG90DQoNCi13ZSBjYW4gc2VlIHRoYXQgdGhlIG1vZGVsIGRpZCBiZXR0ZXIgZm9yIGVjaGVhcGVyIGhvdXNlcyB0aGFuIGNoZWFwZXIgb25lcy4NCm1pZ2h0IGJlIGJlY2F1c2UgdGhleSBhciBtb3JlIGluIG51bWJlciBhbmQgYXJlIGNsYW1wZWQgdG9nZXRoZXIgbW9yZSB0aGFuIGV4cGVuc2l2ZSBvbmVzLiANCg0KLWluIHRoZSBlbmQgdGhlIHdvcmQgZGVzY3JpcHRpb25zIGRpZCBub3QgbWF0dGVyIGFzIG11Y2ggYXMgdGhlIG9uZXMgaW4gZGF0YSBmcmFtZS4NCg0KLWxvY2F0aW9uIGxhdGl0dWRlIHdhcyBtb3N0IGltcG9ydGFudC4gdGhpcyBpcyBjb25zaXN0ZW50IHdpdGggdGhlIG1hcA0KYmVjYXVzZSB0aGUgZnVydGhlciBlYXN0IHlvdSBhcmUgdGhlIGNsb3NlciB5b3UgYXJlIHRvIHRoZSB3YXRlci4NCg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIERpc2N1c3Npb24NCmZyb20gdGhlIHJlZ3Jlc3Npb24gbGluZSwgd2UgY2FuIHNlZSB0aGF0IHRoZSBleHBlbnNpdmUgaG91c2VzIGFyZSB0b28gb3V0bHlpbmcgDQppZiB3ZSBoYWQgcmVtb3ZlZCB0aGVtLCB3ZSBjb3VsZCBoYXZlIGdvdHRlbiBoaWdoZXIgcm1zZS4gdGhlIHBsb3QgY29tZmltcnMgaXQuDQo3NyUgcnNxIG1lYW5zIHRoZXJlIGFyZSBtb3JlIGF0dHJpYnV0ZSB3ZSBjb3VsZCBiZSBtaXNzaW5nLg0KDQpgYGB7cn0NCmBgYHtyfQ==